#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <time.h>
#include "../client/clsrv.h"
#include "server.h"


#define _WRITE_ 1
#define _READ_ 	2


static void siginthandler(int sig);
static void sigusrhandler(int sig);

static int startDB(void);
static int promptForRooms(void);
static int makeAnswer(answerT * answer);
static int createDB(char * dbname, int roomQty);
int processQuestion(requestT * request, answerT * answer);
int GetQuestion(requestT * request);
static int createPidFile(char * filename);

int _question = 0;

int
main(int argc, char * argv[])
{
	int end = 0;
	requestT request;
	answerT answer;
	
	system("clear");
	printf("Welcome!\n\n");
	
	if(startDB() == -1)
		exit(1);
		
	signal(SIGINT, siginthandler);
	signal(SIGUSR1, sigusrhandler);
	//printf("antes while1\n");
	if(createPidFile(_SRVPIDFILE_) == -1)
		return 1;
		
	while (1)
	{
		if(_question)
		{
			if(GetQuestion(&request) < 0)
			{
				fprintf(stderr, "Can't get request from file.\n");
				return 1;
			}
		
			if(processQuestion(&request, &answer) < 0)
			{
				if(makeAnswer(&answer) != -1)
					kill(request.pid, SIGUSR1);
				return 1;
			}
			
			if(request.opcode != LIST && makeAnswer(&answer) == -1)
				return 1;

			if(request.opcode == LIST && answer.status == ENO_BOOK_OCCUP)
				if(makeAnswer(&answer) == -1)
					return -1;
			kill(request.pid, SIGUSR1);
		}
			
	}
	
	return 0;
}

static int
startDB(void)
{
	int roomQty;

	if( !access(_HDB_, F_OK) )
		return 0;
	
	while((roomQty= promptForRooms()) == 0);
	
	createDB(_HDB_, roomQty);
	return 0;
}

static int
promptForRooms(void)
{
	char * line;
	char c;
	int roomQty = 0, leave = 0, wrong = 0, count = 0;

	printf("This is the first time you run HotelDB. Please specify the amount "
			"of rooms: ");
	
	if( (line= malloc(MAXLINE)) == NULL )
		return -1;
	
	while(!leave && !wrong)
	{
		c= getchar();
	
		if( count < MAXLINE && isdigit(c))
			line[count++]= c;
		
		if( c == '\n' && count < MAXLINE )
		{
			line[count]= '\0';
			leave = 1;
		}
		if( c == '\n' )
		{
			count= 0;
			leave = 1;
		}
		if(!isdigit(c) && !leave)
		{
			wrong = 1;
			while(getchar() != '\n');
		}
	}
	
	if(leave && !wrong)
	{
		if(strlen(line))
			roomQty = atoi(line);
	}
		
	free(line);
	return roomQty;

	return 10;
}

static int
createPidFile(char * filename)
{
	int fd, pid;
	
	if ((fd= open(_SRVPIDFILE_, O_RDWR | O_CREAT , 0770)) == -1)
	{
		perror("Can't open server pid file");
		return -1;
	}
	
	pid = getpid();
	if( write(fd, &pid, sizeof(int)) < sizeof(int))
	{
		perror("Can't write pid to new file");
		return -1;
	}
	close(fd);

	return 1;
	
}

static int
makeAnswer(answerT * answer)
{
	int fd;
	
	while ((fd= open(_AFILE_, O_RDWR | O_CREAT | O_EXCL, 0660)) == -1)
		sleep(1);
	
	
	if( write(fd, answer, sizeof(answerT)) < sizeof(answerT))
	{
		perror("Can't write pid to new file");
		return -1;
	}
	
	close(fd);
	
	return 1;
}

static int
createDB(char * dbname, int roomQty)
{
	int db, i, num, fd, pid;
	headerT header;
	roomT room;
	
	header.roomsQty= roomQty;
	header.rRooms= 0;
	header.oRooms= 0;

	room.status= _FREE_;

	if ((db= open(dbname, O_RDWR | O_CREAT | O_EXCL, 0770)) == -1)
	{
		perror("Can't open DB file");
		return -1;
	}
	
	if( write(db, &header, sizeof(headerT)) < sizeof(headerT))
	{
		perror("Can't write header to new DB file");
		return -1;
	}
	
	room.status = 0;
	room.guest[0] = '\0';
	for( i= 0; i < roomQty; i++ )
		if( write(db, &room, sizeof(roomT)) < sizeof(roomT))
		{
			perror("Can't write header to new DB file");
			return -1;
		}
		
	close(db);
		
	return 1;
}

int 
GetQuestion(requestT * request)
{
	int fd;
	
	if ((fd = open(_QFILE_, O_RDWR)) == -1)
	{
		perror("Can't open requests file");
		return -1;
	}
	
	if(read(fd, request, sizeof(requestT)) < sizeof(requestT))
	{
		perror("Can't read request file");
		return -1;
	}
	
	_question--;
	
	return 1;

}

int 
processQuestion(requestT * request, answerT * answer)
{
	int ret = 0;
	
	switch(request->opcode)
	{
		case RESERVE: ret = reservar(request->guest, _HDB_, answer);
					 break;
		
		case OCCUPY: ret = ocupar(request->guest, _HDB_, request->room, answer);
					break;			
		case FREE: ret = liberar(request->guest,request->room, _HDB_, answer);
					break;
		
		case LIST: ret = listar(request->guest, _HDB_, answer);
					break;
			
		default: answer->status = EOPCODE;
				answer->opcode = request->opcode;
				return -1;
	}
	answer->opcode = request->opcode;
	return ret;
}


int
liberar(char * guest, int number, char * dbname, answerT * answer)
{
	int db, i;
	headerT head;
	roomT room;
	answer->status = 0;
	if(guest == NULL || dbname == NULL || number <= 0)
	{
		answer->status = EFREE_ROOM;
		return -1;
	}
	
	if ((db = open(dbname, O_RDWR)) == -1)
	{
		answer->status = EOPEN_DB;
		return -1;
	}
	
	if(lockFile(db, _WRITE_ , number) == -1)
	{
		answer->status = ELOCK_DB;
		return -1;
	}
	
	if (read(db, &head, sizeof(headerT)) < sizeof(headerT))
	{
		answer->status = EINCON_DB;
		return -1;
	}
	
	if(head.rRooms + head.oRooms == 0)
	{
		answer->status = ENO_BOOK_OCCUP;
		return 0;
	}
	
	lseek(db, sizeof(headerT) + (number-1) * sizeof(roomT), SEEK_SET);
	
	if (read(db, &room, sizeof(roomT)) < sizeof(roomT))
	{
		answer->status = EINCON_DB;
		return -1;
	}
	
	if(room.status == _OCCUPIED_ && !strcmp(room.guest, guest))
	{
		time_t rawtime;
		char * left;
		char occup[60];
		
		time(&rawtime);
		left = ctime(&rawtime);
		strcpy(occup, room.occupied);
		
		room.status = _FREE_;
		room.guest[0] = '\0';
		
		lseek(db, sizeof(headerT) + (number-1) * sizeof(roomT), SEEK_SET);
		
		if( write(db, &room, sizeof(roomT)) < sizeof(roomT))
		{
			answer->status = EFREE_ROOM;
			return -1;
		}
		
		head.oRooms--;
		lseek(db, 0, SEEK_SET);
		if( write(db, &head, sizeof(headerT)) < sizeof(headerT))
		{
			answer->status = EFREE_ROOM;
			return -1;
		}
		
		answer->opcode = FREE;
		answer->status = 0;
		answer->room = number;
		strcpy(answer->guest, guest);
		strcpy(answer->etime, occup);
		strcpy(answer->ltime, left);
	}
	else
		answer->status = ENOT_OCCUP;
		
	
	unlockFile(db, 0);
		
	close(db);
	return 1;
}


int
ocupar(char * guest, char * dbname, int number, answerT * answer)
{
	int db, i;
	headerT head;
	roomT room;
	answer->status = 0;
	if(guest == NULL || dbname == NULL || number <= 0)
	{
		answer->status = EFREE_ROOM;
		return -1;
	}
	
	if ((db = open(dbname, O_RDWR)) == -1)
	{
		answer->status = EOPEN_DB;
		return -1;
	}
	
	if(lockFile(db, _WRITE_ , number) == -1)
	{
		answer->status = ELOCK_DB;
		return -1;
	}
	
	if (read(db, &head, sizeof(headerT)) < sizeof(headerT))
	{
		answer->status = EINCON_DB;
		return -1;
	}
	
	if(head.roomsQty == head.oRooms)
	{
		answer->status = ENO_FREE;
		return 0;
	}
	if(head.rRooms == 0)
	{
		answer->status = ENO_BOOK;
		return 0;
	}
	
	lseek(db, sizeof(headerT) + (number-1) * sizeof(roomT), SEEK_SET);
	
	if (read(db, &room, sizeof(roomT)) < sizeof(roomT))
	{
		answer->status = EINCON_DB;
		return -1;
	}
	
	if(room.status == _BOOKED_ && !strcmp(room.guest, guest))
	{
		time_t rawtime;
		
		room.status = _OCCUPIED_;
		time(&rawtime);
		strcpy(room.occupied, ctime(&rawtime));
		
		lseek(db, sizeof(headerT) + (number-1) * sizeof(roomT), SEEK_SET);
		
		if( write(db, &room, sizeof(roomT)) < sizeof(roomT))
		{
			answer->status = EOCCUP_ROOM;
			return -1;
		}
		
		head.rRooms--;
		head.oRooms++;
		lseek(db, 0, SEEK_SET);
		if( write(db, &head, sizeof(headerT)) < sizeof(headerT))
		{
			answer->status = EOCCUP_ROOM;
			return -1;
		}
		answer->room = number;
		strcpy(answer->guest, guest);
		strcpy(answer->etime, room.occupied);
	}
	else
		answer->status = ENOT_BOOK;
		
	unlockFile(db, 0);
		
	close(db);		
	return 1;
}

int
reservar(char * guest, char * dbname, answerT * answer)
{
	int db, reserved = 0, i;
	headerT head;
	roomT room;
	
	answer->status = 0;
	if(guest == NULL || dbname == NULL)
	{
		answer->status = EBOOK_ROOM;
		return -1;
	}
	
	if ((db = open(dbname, O_RDWR)) == -1)
	{
		answer->status = EOPEN_DB;
		return -1;
	}
	
	if(lockFile(db, _WRITE_ , 0) == -1)
	{
		answer->status = ELOCK_DB;
		return -1;
	}
	
	if (read(db, &head, sizeof(headerT)) < sizeof(headerT))
	{
		answer->status = EINCON_DB;
		return -1;
	}
	
	if(head.roomsQty == head.oRooms + head.rRooms)
	{
		answer->status = ENO_FREE;
		return 0;
	}
	
	for(i = 0; !reserved && i < head.roomsQty; i++)
	{
		if (read(db, &room, sizeof(roomT)) < sizeof(roomT))
		{
			answer->status = EINCON_DB;
			return -1;
		}
		
		if(room.status == _FREE_)
		{
			room.status = _BOOKED_;
			strcpy(room.guest, guest);
			lseek(db, sizeof(headerT) + i * sizeof(roomT), SEEK_SET);
			
			if( write(db, &room, sizeof(roomT)) < sizeof(roomT))
			{
				answer->status = EBOOK_ROOM;
				return -1;
			}
			
			head.rRooms++;
			
			lseek(db, 0, SEEK_SET);
			if( write(db, &head, sizeof(headerT)) < sizeof(headerT))
			{
				answer->status = EBOOK_ROOM;
				return -1;
			}
			
			answer->room = i+1;
			strcpy(answer->guest, guest);
			
			reserved = 1;
		}
		
	}
	
	unlockFile(db, 0);
	
	close(db);
	
	return 1;
}

int
listar(char * guest, char * dbname, answerT * answer)
{
	int db, i, fd;
	headerT head;
	roomT room;
	char aux[20];
	
	if(guest == NULL || dbname == NULL || answer == NULL)
	{
		answer->status = ELIST_ROOM;
		return -1;
	}
	
	if ((db = open(dbname, O_RDWR)) == -1)
	{
		answer->status = EOPEN_DB;
		return -1;
	}
	
	if(lockFile(db, _WRITE_ , 0) == -1)
	{
		answer->status = ELOCK_DB;
		return -1;
	}

	answer->status = OK;
	strcpy(answer->guest, guest);
	answer->opcode = LIST;	
	if (read(db, &head, sizeof(headerT)) < sizeof(headerT))
	{
		answer->status = EINCON_DB;
		return -1;
	}

	if( !head.oRooms && !head.rRooms)
	{
		answer->status = ENO_BOOK_OCCUP;
		return 0;
	}

	if(makeAnswer(answer) == -1)
		return -1;

	if((fd= open(_AFILE_, O_RDWR)) == -1)
	{
		answer->status = EOPEN_DB; //TODO aca tendria que ir otro status
		return -1;
	}
	lseek(fd, sizeof(answerT), SEEK_SET);
		
	for(i = 0; i < head.roomsQty; i++)
	{
		if (read(db, &room, sizeof(roomT)) < sizeof(roomT))
		{
			answer->status = EINCON_DB;
			return -1;
		}
		
		if(!strcmp(room.guest, guest))
		{
			int len;
			sprintf(aux, "Room %d %s\n", i+1, room.status == _BOOKED_ ?
									 "BOOKED" : "OCCUPPIED");
			
			len = strlen(aux); 
			if( write(fd, aux, len) < len)
			{
				perror("Can't write to answer file");
				return -1;
			}
		}
		
	}
	
	unlockFile(db, 0);
	close(fd);
	close(db);
	
	return 1;
}


/* Extra functions */
static void 
siginthandler(int sig)
{
	printf("\nExit!\n");
	exit(1);
}

static void sigusrhandler(int sig)
{
	_question++;
	return;
}
int
lockFile(int fd, int rorw, int room)
{
	struct flock my_lock;
	
	if(fd < 0 || (rorw != _WRITE_ && rorw != _READ_) || room < 0)
		return -1;
	
	my_lock.l_type = F_WRLCK;
	my_lock.l_whence = SEEK_SET;
	my_lock.l_start = 0;
	my_lock.l_len = sizeof(headerT);
	
	if(fcntl(fd, F_SETLKW, &my_lock) == -1)
	{
		perror("Can't lock DB file");
		return -1;
	}
	
	my_lock.l_type = rorw == _WRITE_ ? F_WRLCK : F_RDLCK;
	my_lock.l_whence = SEEK_SET;
	my_lock.l_start =  room == 0 ? sizeof(headerT) : sizeof(headerT) + 
											(room - 1) * sizeof(roomT);
	my_lock.l_len = room == 0 ? 0 : sizeof(roomT);
	
	
	if(fcntl(fd, F_SETLKW, &my_lock) == -1)
	{
		perror("Can't lock DB file");
		return -1;
	}
	
	return 1;
}

int
unlockFile(int fd, int room)
{
	struct flock my_lock;
	
	if(fd < 0 ||  room < 0)
		return -1;
	
	my_lock.l_type = F_UNLCK;
	my_lock.l_whence = SEEK_SET;
	my_lock.l_start = 0;
	my_lock.l_len = sizeof(headerT);
	
	if(fcntl(fd, F_SETLK, &my_lock) == -1)
	{
		perror("Can't unlock DB file");
		return -1;
	}
	
	my_lock.l_type =  F_UNLCK;
	my_lock.l_whence = SEEK_SET;
	my_lock.l_start = room == 0 ? sizeof(headerT) : sizeof(headerT) + 
											(room - 1) * sizeof(roomT);
	my_lock.l_len = room == 0 ? 0 : sizeof(roomT);
	
	
	if(fcntl(fd, F_SETLK, &my_lock) == -1)
	{
		perror("Can't unlock DB file");
		return -1;
	}
	
	return 1;
}

